Skip to content

feat: concurrent multi-tool daemon support (#12)#22

Closed
don-petry wants to merge 4 commits intoJoaolfelicio:mainfrom
don-petry:feat/concurrent-multi-tool
Closed

feat: concurrent multi-tool daemon support (#12)#22
don-petry wants to merge 4 commits intoJoaolfelicio:mainfrom
don-petry:feat/concurrent-multi-tool

Conversation

@don-petry
Copy link
Copy Markdown
Collaborator

@don-petry don-petry commented Apr 5, 2026

Why

Users who work across multiple AI tools (Gemini, Claude, Copilot) must currently run separate daemon instances for each one. This is cumbersome to manage and wastes resources — each instance runs its own MCP client connection and evaluator. A single daemon that monitors all tools concurrently with shared infrastructure is simpler to operate and more resource-efficient.

Summary

  • Adds --tools CLI option to run multiple providers concurrently in a single daemon (e.g. --tools gemini-cli,claude,copilot)
  • Each provider watches in its own asyncio.create_task, feeding interactions into a shared bounded asyncio.Queue(maxsize=1000)
  • Shared MCP client and evaluator across all providers
  • Dashboard header shows all active tools; status messages prefixed with [tool_name]
  • --tool (single) still works for backward compatibility

Closes #12

Testing evidence (live, Python 3.12 + mcp, claude + copilot CLIs)

Check Result
Full test suite: 75/75 passed ✅ PASS
Live: create single claude provider ✅ PASS
Live: create multiple providers (claude + copilot) ✅ PASS
Live: unknown tool raises ValueError with available list ✅ PASS
Live: --tools claude,copilot accepted by CLI ✅ PASS
Live: --tools invalid_tool rejected with error ✅ PASS
Live: --tools claude,claude,copilot deduplicates ✅ PASS
Live: --tools ,,, (empty) rejected ✅ PASS
Live: --tool claude backward compat works ✅ PASS
TOOL_REGISTRY has all 3 tools (gemini-cli, copilot, claude) ✅ PASS

Issues found and fixed during testing

  • test_daemons.py: TOOL_REGISTRY holds import-time class refs → fixed to patch _create_providers
  • _watch_provider: KeyboardInterrupt not caught (not subclass of Exception) → added to except clause

🤖 Generated with Claude Code

Adds --tools option to run multiple providers concurrently in a single
daemon process. Each provider watches in its own async task, feeding
interactions into a shared asyncio.Queue for unified processing.

Key changes:
- TOOL_REGISTRY maps tool names to (provider_class, bootstrap_fn) pairs
- _create_providers() bootstraps and instantiates multiple providers
- run_daemon() creates watcher tasks per provider via asyncio.create_task
- Dashboard shows all active tools in header
- --tool still works for single-tool backward compat
- --tools validates tool names and rejects unknowns

Usage: context-scribe --tools gemini-cli,claude,copilot

Closes Joaolfelicio#12

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 5, 2026 03:41
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for running multiple log providers concurrently within a single context-scribe daemon instance, using a shared evaluator + Memory Bank client and an interaction fan-in queue.

Changes:

  • Introduces a TOOL_REGISTRY and _create_providers() to bootstrap/instantiate providers from tool names.
  • Adds --tools (CSV) CLI option and updates the daemon loop to run one watcher task per provider, feeding a shared asyncio.Queue.
  • Adds new unit tests covering registry population and provider creation behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

File Description
context_scribe/main.py Adds registry/provider factory + concurrent watcher tasks + --tools CLI parsing and multi-tool dashboard labeling.
tests/test_multi_tool.py Adds tests for TOOL_REGISTRY and _create_providers() behavior (single/multi/unknown/bootstrap).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread context_scribe/main.py
Comment thread context_scribe/main.py
Comment thread context_scribe/main.py Outdated
Comment thread context_scribe/main.py Outdated
Comment thread context_scribe/main.py
Comment thread context_scribe/main.py
- _create_providers() now raises ValueError for unknown tools
- Queue bounded to maxsize=1000 to prevent memory growth
- _watch_provider() catches StopIteration and exceptions gracefully
- watcher_tasks initialized before try to prevent UnboundLocalError
- --tools deduplicates and rejects empty input

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@don-petry
Copy link
Copy Markdown
Collaborator Author

@Joaolfelicio - What do you think about this enhancement idea?

- test_daemons.py: patch _create_providers instead of individual class
  names, since TOOL_REGISTRY captures class refs at import time
- _watch_provider: catch KeyboardInterrupt from mock generators
- Increase test wait timeout for queue-based async pipeline

Found via full test suite run with Python 3.12 + mcp installed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 5, 2026 17:32
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread context_scribe/main.py
Comment thread context_scribe/main.py Outdated
Comment thread context_scribe/main.py
Comment thread tests/test_daemons.py Outdated
Comment thread tests/test_multi_tool.py
- Reject empty/whitespace --tools values by checking `tools_csv is not None`
- Fail fast in run_daemon() when tools=[] instead of silent fallback
- Add CLI unit tests for --tools deduplication, invalid, and empty input
- Remove unused provider_class from test_daemons.py parametrize
- Fix misleading docstring in test_multi_tool.py fixture

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
don-petry pushed a commit that referenced this pull request Apr 10, 2026
Resolve merge conflict in tests/test_daemons.py, keeping the
_create_providers mock approach from #22 while incorporating the
os._exit mock and reduced iterations from #19.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator Author

@don-petry don-petry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review — NEEDS HUMAN REVIEW

Risk: MEDIUM
Reviewed commit: d15609dabf906426a9c8a758429c6d322913d9f6
Council vote: security=MEDIUM · correctness=MEDIUM · maintainability=MEDIUM

Summary

The PR implements multi-tool concurrent daemon support (issue #12) cleanly — the TOOL_REGISTRY pattern, async queue fan-in, and 75 passing tests all look solid, and no security-sensitive surfaces were touched. However, the PR cannot be merged in its current state: the branch has conflicts with main (DIRTY merge state). Beyond the merge conflict, two correctness issues need attention: watcher tasks are cancelled but never awaited in the shutdown finally block (risk of use-after-close races), and generator cleanup relies on __del__ rather than explicit close(). Documentation also lags the implementation — CONTRIBUTING.md still describes the old if/elif tool-registration flow that the new TOOL_REGISTRY has replaced.

Linked issue analysis

Issue #12 — multi-tool concurrent daemon support: All acceptance criteria are addressed. The --tools CSV option is wired, TOOL_REGISTRY replaces the old per-tool dispatch, providers run as independent watcher tasks sharing a fan-in queue, and the shared evaluator + Memory Bank client are reused across tools. Tests cover registry population, provider creation (single/multi/unknown/bootstrap), and the daemon lifecycle.

Findings

MAJOR

  • [major] (branch-level)Merge conflict (correctness · maintainability): PR is in CONFLICTING / DIRTY state against main. Must be rebased before it can land.
  • [major] context_scribe/main.py:336Async lifecycle (correctness): Watcher tasks are cancelled via task.cancel() but never awaited in the finally block. mcp_client.close() proceeds immediately, risking use-after-close errors and leaving CancelledError exceptions unobserved. Fix: await asyncio.gather(*tasks, return_exceptions=True) after cancellation.
  • [major] CONTRIBUTING.mdDocumentation drift (maintainability): CONTRIBUTING.md still instructs contributors to add tools via the old if/elif logic in run_daemon. The new TOOL_REGISTRY registration pattern replaces that workflow but is entirely undocumented.

MINOR

  • [minor] context_scribe/main.py:263Resource leak (correctness): Generator cleanup for provider.watch() relies on __del__ after task cancellation. Python does not guarantee prompt __del__, which can leak watchdog Observer threads. Fix: explicitly call watch_iter.close() in the cancel/finally path.
  • [minor] context_scribe/main.py:264Deprecated API (correctness · maintainability): asyncio.get_event_loop() called inside async functions _watch_provider() and _loop(). On Python 3.10+ (this project targets 3.12), use asyncio.get_running_loop() from a running coroutine.
  • [minor] context_scribe/main.py:242Evaluator selection (correctness): _detect_evaluator(tool_names[0]) uses only the first tool when multiple tools are provided. --tools gemini-cli,claude would select a Gemini evaluator for Claude interactions, producing incorrect rule extraction.
  • [minor] README.mdDocumentation drift (maintainability): Usage section shows only --tool examples; the new --tools multi-tool option is not mentioned.
  • [minor] context_scribe/main.pyMissing type hints (maintainability): _create_providers has no return type annotation; _watch_provider's provider parameter has no type hint. CONTRIBUTING.md requires type hints for all function signatures.

INFO

  • [info] context_scribe/main.py--tools validated against TOOL_REGISTRY keys with dedup and empty-input rejection; no injection surface. (security)
  • [info] context_scribe/main.pyasyncio.Queue(maxsize=1000) bounds memory growth from provider watchers. (security)
  • [info] (PR comment) — Open collaborator question from don-petry (2026-04-05) has not been answered. (correctness)
  • [info] tests/test_daemons.pytest_run_daemon_tools patches bootstrap_global_config even though _create_providers is fully mocked, making the bootstrap patch a no-op. (correctness · maintainability)
  • [info] context_scribe/main.pyfrom typing import List used; prefer built-in list[str] on Python 3.10+. (maintainability)
  • [info] tests/test_multi_tool.pytest_create_providers_calls_bootstrap mutates TOOL_REGISTRY directly instead of patch.dict; safer against unexpected exceptions. (maintainability)
  • [info] context_scribe/main.py_detect_evaluator is called with tool_names[0] when multiple tools are provided — a comment explaining this choice would help future maintainers. (maintainability)

CI status

2 checks passing (Detect ecosystems, pip-audit), 4 skipped (dependabot, npm audit, govulncheck, cargo audit). No test-suite check appears in the rollup for this commit — branch is CONFLICTING / DIRTY against main, so merge-commit verification has not run. All 75 unit tests passed on the PR branch head per test logs.


Reviewed automatically by the don-petry PR-review council (security: opus 4.6 · correctness: sonnet 4.6 · maintainability: sonnet 4.6 · synthesis: sonnet 4.6). The marker on line 1 lets the agent detect new commits and re-review. Reply with @don-petry if you need a human.

@don-petry don-petry added the needs-human-review Flagged by automated PR review agent label Apr 10, 2026
@don-petry
Copy link
Copy Markdown
Collaborator Author

Replaced by #28 — the fork (don-petry/context-scribe) is archived and read-only, so this branch can't be updated. #28 contains the same changes rebased on current main (including PR #19's merge) with all review comments addressed.

@don-petry don-petry closed this Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-human-review Flagged by automated PR review agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Concurrent multi-tool daemon support

2 participants